aboutsummaryrefslogtreecommitdiff
path: root/pages/en/search/[...param].tsx
diff options
context:
space:
mode:
Diffstat (limited to 'pages/en/search/[...param].tsx')
-rw-r--r--pages/en/search/[...param].tsx598
1 files changed, 598 insertions, 0 deletions
diff --git a/pages/en/search/[...param].tsx b/pages/en/search/[...param].tsx
new file mode 100644
index 0000000..5a34ff5
--- /dev/null
+++ b/pages/en/search/[...param].tsx
@@ -0,0 +1,598 @@
+import { Key, useEffect, useRef, useState } from "react";
+import { motion as m } from "framer-motion";
+import Skeleton from "react-loading-skeleton";
+import { useRouter } from "next/router";
+import Link from "next/link";
+import Head from "next/head";
+import Footer from "@/components/shared/footer";
+
+import Image from "next/image";
+import { aniAdvanceSearch } from "@/lib/anilist/aniAdvanceSearch";
+import MultiSelector from "@/components/search/dropdown/multiSelector";
+import SingleSelector from "@/components/search/dropdown/singleSelector";
+import {
+ animeFormatOptions,
+ formatOptions,
+ genreOptions,
+ mangaFormatOptions,
+ mediaType,
+ seasonOptions,
+ tagsOption,
+ yearOptions,
+} from "@/components/search/selection";
+import InputSelect from "@/components/search/dropdown/inputSelect";
+import { Cog6ToothIcon, TrashIcon } from "@heroicons/react/20/solid";
+import useDebounce from "@/lib/hooks/useDebounce";
+import { Navbar } from "@/components/shared/NavBar";
+import MobileNav from "@/components/shared/MobileNav";
+import SearchByImage, {
+ TraceMoeResultTypes,
+} from "@/components/search/searchByImage";
+import { PlayIcon } from "@heroicons/react/24/outline";
+import { StaticImport } from "next/dist/shared/lib/get-img-props";
+
+export async function getServerSideProps(context: any) {
+ const { param } = context.query;
+
+ const { search, format, genres, season, year } = context.query;
+
+ let getFormat, getSeason, getYear;
+ let getGenres = [];
+
+ if (genres) {
+ const gr = genreOptions.find(
+ (i) => i.value.toLowerCase() === genres.toLowerCase()
+ );
+ getGenres.push(gr);
+ }
+
+ if (season) {
+ getSeason = seasonOptions.find(
+ (i) => i.value.toLowerCase() === season.toLowerCase()
+ );
+ if (!year) {
+ const now = new Date().getFullYear();
+ getYear = yearOptions.find((i) => i.value === now.toString());
+ } else {
+ getYear = yearOptions.find((i) => i.value === year);
+ }
+ }
+
+ if (format) {
+ getFormat = formatOptions.find(
+ (i) => i.value.toLowerCase() === format.toLowerCase()
+ );
+ }
+
+ if (!param && param.length !== 1) {
+ return {
+ notFound: true,
+ };
+ }
+
+ const typeIndex = param[0] === "anime" ? 0 : 1;
+
+ return {
+ props: {
+ index: typeIndex,
+ query: search || null,
+ formats: getFormat || null,
+ seasons: getSeason || null,
+ years: getYear || null,
+ genres: getGenres || null,
+ },
+ };
+}
+
+type CardProps = {
+ index: number;
+ query: string;
+ genres: any;
+ formats: any;
+ seasons: any;
+ years: any;
+};
+
+export default function Card({
+ index,
+ query,
+ genres,
+ formats,
+ seasons,
+ years,
+}: CardProps) {
+ const inputRef = useRef(null);
+ const router = useRouter();
+
+ const [data, setData] = useState<any>();
+ const [imageSearch, setImageSearch] = useState<TraceMoeResultTypes[]>();
+
+ const [loading, setLoading] = useState(true);
+
+ const [search, setQuery] = useState<string | null | undefined>(query);
+ const debounceSearch = useDebounce(search, 500);
+
+ const [type, setSelectedType] = useState<{
+ name: string;
+ value: string;
+ } | null>(mediaType[index]);
+ const [year, setYear] = useState(years);
+ const [season, setSeason] = useState(seasons);
+ const [sort, setSelectedSort] = useState<{ name: string; value: string }>();
+ const [genre, setGenre] = useState(genres);
+ const [format, setFormat] = useState(formats);
+
+ const [isVisible, setIsVisible] = useState(false);
+
+ const [page, setPage] = useState(1);
+ const [nextPage, setNextPage] = useState(true);
+
+ async function advance() {
+ setLoading(true);
+ const data = await aniAdvanceSearch({
+ search: debounceSearch,
+ type: type?.value as "ANIME" | "MANGA" | undefined,
+ genres: genre,
+ page: page,
+ sort: sort?.value,
+ format: format?.value,
+ season: season?.value,
+ seasonYear: year?.value,
+ });
+ if (data?.media?.length === 0) {
+ setNextPage(false);
+ setLoading(false);
+ } else if (data !== null && page > 1) {
+ setData((prevData: any) => {
+ return [...(prevData ?? []), ...data?.media];
+ });
+ setNextPage(data?.pageInfo.hasNextPage);
+ setLoading(false);
+ } else {
+ setData(data?.media);
+ setNextPage(data?.pageInfo.hasNextPage);
+ setLoading(false);
+ }
+ }
+
+ useEffect(() => {
+ setData(null);
+ setPage(1);
+ setNextPage(true);
+ if (page === 1) {
+ advance();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [
+ debounceSearch,
+ type?.value,
+ sort?.value,
+ genre,
+ format?.value,
+ season?.value,
+ year?.value,
+ ]);
+
+ useEffect(() => {
+ if (imageSearch) return;
+ if (page > 1) {
+ advance();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [page, imageSearch]);
+
+ useEffect(() => {
+ function handleScroll() {
+ if (imageSearch) {
+ window.removeEventListener("scroll", handleScroll);
+ return;
+ }
+ if (page > 10 || !nextPage) {
+ window.removeEventListener("scroll", handleScroll);
+ return;
+ }
+
+ if (
+ window.innerHeight + window.pageYOffset >=
+ document.body.offsetHeight - 3
+ ) {
+ if (!loading) {
+ setPage((prevPage) => prevPage + 1);
+ }
+ }
+ }
+
+ window.addEventListener("scroll", handleScroll);
+
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, [page, nextPage, imageSearch, loading]);
+
+ const handleKeyDown = async (event: any) => {
+ if (event.key === "Enter") {
+ event.preventDefault();
+ const inputValue = event.target.value;
+ if (inputValue === "") {
+ setQuery(undefined);
+ } else {
+ setQuery(inputValue);
+ }
+ }
+ };
+
+ function trash() {
+ setImageSearch(undefined);
+ setQuery(undefined);
+ setGenre(undefined);
+ setFormat(undefined);
+ setSelectedSort(undefined);
+ setSeason(undefined);
+ setYear(undefined);
+ router.push(`/en/search/${mediaType[index]?.value?.toLowerCase()}`);
+ }
+
+ function handleVisible() {
+ setIsVisible(!isVisible);
+ }
+
+ const handleVideoHover = (hovered: boolean, id: any) => {
+ const updatedImageSearch = imageSearch?.map((item: any) => {
+ if (item.filename === id) {
+ return { ...item, hovered };
+ }
+ return item;
+ });
+ setImageSearch(updatedImageSearch);
+ };
+
+ // console.log({ loading, data });
+
+ return (
+ <>
+ <Head>
+ <title>Moopa - search</title>
+ <meta name="title" content="Search" />
+ <meta name="description" content="Search your favourites Anime/Manga" />
+ <link rel="icon" href="/svg/c.svg" />
+ </Head>
+
+ <Navbar
+ scrollP={10}
+ withNav={true}
+ shrink={true}
+ paddingY="py-1 lg:py-3"
+ />
+ <MobileNav hideProfile={true} />
+ <main className="w-screen min-h-screen z-40 py-14 lg:py-24">
+ <div className="max-w-screen-xl flex flex-col gap-3 mx-auto">
+ <div className="w-full flex justify-between items-end gap-2 my-3 lg:gap-10 px-5 xl:px-0 relative">
+ <div className="hidden lg:flex items-end w-full gap-5 z-50">
+ <InputSelect
+ inputRef={inputRef}
+ data={mediaType}
+ label="Search"
+ keyDown={handleKeyDown}
+ query={search}
+ setQuery={setQuery}
+ selected={type}
+ setSelected={setSelectedType}
+ />
+ {/* GENRES */}
+ <MultiSelector
+ data={genreOptions}
+ other={tagsOption}
+ selected={genre}
+ setSelected={setGenre}
+ label="Genres"
+ inputRef={inputRef}
+ />
+ {/* SORT */}
+ {/* <SingleSelector
+ data={sortOptions}
+ selected={sort}
+ setSelected={setSelectedSort}
+ label="Sort"
+ /> */}
+ {/* FORMAT */}
+ <SingleSelector
+ data={index === 0 ? animeFormatOptions : mangaFormatOptions}
+ selected={format}
+ setSelected={setFormat}
+ label="Format"
+ />
+ {/* SEASON */}
+ <SingleSelector
+ data={seasonOptions}
+ selected={season}
+ setSelected={setSeason}
+ label="Season"
+ />
+ {/* YEAR */}
+ <SingleSelector
+ data={yearOptions}
+ selected={year}
+ setSelected={setYear}
+ label="Year"
+ />
+ </div>
+ <div className="w-full lg:hidden">
+ <InputSelect
+ inputRef={inputRef}
+ data={mediaType}
+ label="Search"
+ keyDown={handleKeyDown}
+ query={search}
+ setQuery={setQuery}
+ selected={type}
+ setSelected={setSelectedType}
+ />
+ </div>
+
+ <div className="flex gap-2">
+ <div
+ className="lg:hidden py-2 px-2 bg-secondary rounded flex justify-center items-center cursor-pointer hover:bg-opacity-75 transition-all duration-100 group"
+ onClick={handleVisible}
+ >
+ <Cog6ToothIcon className="w-5 h-5" />
+ </div>
+ <SearchByImage setMedia={setData} setData={setImageSearch} />
+ <div
+ className="py-2 px-2 bg-secondary rounded flex justify-center items-center cursor-pointer hover:bg-opacity-75 transition-all duration-100 group"
+ onClick={trash}
+ >
+ <TrashIcon className="w-5 h-5" />
+ </div>
+ </div>
+ </div>
+ {isVisible && (
+ <div className="lg:hidden w-full flex justify-center z-40">
+ <div className="grid grid-cols-2 grid-rows-2 place-items-center w-full px-5 z-30 gap-4">
+ {/* GENRES */}
+ <MultiSelector
+ data={genreOptions}
+ other={tagsOption}
+ selected={genre}
+ setSelected={setGenre}
+ label="Genres"
+ inputRef={inputRef}
+ />
+ {/* SORT */}
+ {/* <SingleSelector
+ data={sortOptions}
+ selected={sort}
+ setSelected={setSelectedSort}
+ label="Sort"
+ /> */}
+ {/* FORMAT */}
+ <SingleSelector
+ data={index === 0 ? animeFormatOptions : mangaFormatOptions}
+ selected={format}
+ setSelected={setFormat}
+ label="Format"
+ />
+ {/* SEASON */}
+ <SingleSelector
+ data={seasonOptions}
+ selected={season}
+ setSelected={setSeason}
+ label="Season"
+ />
+ {/* YEAR */}
+ <SingleSelector
+ data={yearOptions}
+ selected={year}
+ setSelected={setYear}
+ label="Year"
+ />
+ </div>
+ </div>
+ )}
+ {/* <div> */}
+ <div className="flex flex-col gap-14 items-center z-30 overflow-x-hidden">
+ <div
+ key="card-keys"
+ className={`${
+ imageSearch ? "hidden" : ""
+ } grid pt-3 px-5 xl:px-0 xxs:grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-6 justify-items-center grid-cols-2 w-screen xl:w-auto xl:gap-7 gap-5 gap-y-10`}
+ >
+ {loading
+ ? ""
+ : !data && (
+ <div className="w-full text-[#ff7f57] col-span-6 items-center flex justify-center text-center font-bold font-karla xl:text-2xl">
+ Oops!<br></br> Nothing's Found...
+ </div>
+ )}
+
+ {data &&
+ data?.length > 0 &&
+ !imageSearch &&
+ data?.map(
+ (
+ anime: {
+ format: string;
+ id: any;
+ title: { userPreferred: string };
+ coverImage: { extraLarge: string | StaticImport };
+ status: string;
+ episodes: any;
+ chapters: any;
+ },
+ index: Key | null | undefined
+ ) => {
+ return (
+ <m.div
+ initial={{ scale: 0.98 }}
+ animate={{ scale: 1, transition: { duration: 0.35 } }}
+ className="w-full"
+ key={index}
+ >
+ <Link
+ href={
+ anime.format === "MANGA" || anime.format === "NOVEL"
+ ? `/en/manga/${anime.id}`
+ : `/en/anime/${anime.id}`
+ }
+ title={anime.title.userPreferred}
+ className="block relative overflow-hidden bg-secondary hover:scale-[1.03] scale-100 transition-all cursor-pointer duration-200 ease-out rounded"
+ style={{
+ paddingTop: "145%", // 2:3 aspect ratio (3/2 * 100%)
+ }}
+ >
+ <Image
+ className="object-cover"
+ src={anime.coverImage.extraLarge}
+ alt={anime.title.userPreferred}
+ sizes="(min-width: 808px) 50vw, 100vw"
+ quality={100}
+ fill
+ />
+ </Link>
+ <Link
+ href={
+ anime.format === "MANGA" || anime.format === "NOVEL"
+ ? `/en/manga/${anime.id}`
+ : `/en/anime/${anime.id}`
+ }
+ title={anime.title.userPreferred}
+ >
+ <h1 className="font-outfit font-bold xl:text-base text-[15px] pt-4 line-clamp-2">
+ {anime.status === "RELEASING" ? (
+ <span className="dots bg-green-500" />
+ ) : anime.status === "NOT_YET_RELEASED" ? (
+ <span className="dots bg-red-500" />
+ ) : null}
+ {anime.title.userPreferred}
+ </h1>
+ </Link>
+ <h2 className="font-outfit xl:text-[15px] text-[11px] font-light pt-2 text-[#8B8B8B]">
+ {anime.format || <p>-</p>} &#183;{" "}
+ {anime.status || <p>-</p>} &#183;{" "}
+ {anime.episodes
+ ? `${anime.episodes || "N/A"} Episodes`
+ : `${anime.chapters || "N/A"} Chapters`}
+ </h2>
+ </m.div>
+ );
+ }
+ )}
+
+ {loading && (
+ <>
+ {[1, 2, 4, 5, 6, 7, 8].map((item) => (
+ <div className="w-full" key={item}>
+ <div className="w-full">
+ <Skeleton
+ className="w-full rounded"
+ style={{
+ paddingTop: "140%", // 2:3 aspect ratio (3/2 * 100%)
+ width: "(min-width: 808px) 50vw, 100vw",
+ lineHeight: 1,
+ }}
+ />
+ </div>
+ <div>
+ <h1 className="font-outfit w-[320px] font-bold xl:text-base text-[15px] pt-4 line-clamp-2">
+ <Skeleton width={120} height={26} />
+ </h1>
+ </div>
+ </div>
+ ))}
+ </>
+ )}
+ </div>
+
+ {imageSearch && (
+ <div className="grid grid-cols-1 xs:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-4 gap-3 md:gap-7 px-5 lg:px-0">
+ {imageSearch.map((a, index) => {
+ return (
+ <m.div
+ key={index}
+ initial={{ scale: 0.9 }}
+ animate={{ scale: 1, transition: { duration: 0.35 } }}
+ className="flex flex-col gap-2 shrink-0 cursor-pointer relative group/item"
+ >
+ <Link
+ className="relative aspect-video rounded-md overflow-hidden group"
+ href={`/en/anime/${a.anilist.id}`}
+ onMouseEnter={() => {
+ handleVideoHover(true, a.filename);
+ }}
+ onMouseLeave={() => handleVideoHover(false, a.filename)}
+ >
+ <div className="w-full h-full bg-gradient-to-t from-black/70 from-20% to-transparent group-hover:to-black/40 transition-all duration-300 ease-out absolute z-30" />
+ <div className="absolute bottom-3 left-0 mx-2 text-white flex gap-2 items-center w-[80%] z-30">
+ <PlayIcon className="w-5 h-5 shrink-0" />
+ <h1
+ className="font-semibold font-karla line-clamp-1"
+ title={a?.anilist.title.romaji}
+ >
+ {`Episode ${a.episode}`}
+ </h1>
+ </div>
+
+ {a?.image && (
+ <Image
+ src={a?.image}
+ width={200}
+ height={200}
+ alt="Episode Thumbnail"
+ className={`w-full object-cover group-hover:scale-[1.02] duration-300 ease-out z-10 ${
+ !a.hovered ? "visible" : "hidden"
+ }`}
+ />
+ )}
+ {a?.video && (
+ <video
+ src={a.video}
+ className={`w-full object-cover group-hover:scale-[1.02] duration-300 ease-out z-10 ${
+ a.hovered ? "visible" : "hidden"
+ }`}
+ autoPlay
+ muted
+ loop
+ playsInline
+ />
+ )}
+ </Link>
+
+ <Link
+ className="flex flex-col font-karla w-full"
+ href={`/en/anime/${a.anilist.id}`}
+ >
+ {/* <h1 className="font-semibold">{a.title}</h1> */}
+ <p className="flex items-center gap-1 text-sm text-gray-400 max-w-[320px]">
+ <span
+ className="text-white max-w-[120px] md:max-w-[200px] lg:max-w-[220px]"
+ style={{
+ display: "inline-block",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ whiteSpace: "nowrap",
+ }}
+ title={a?.anilist.title.romaji}
+ >
+ {a?.anilist.title.romaji}
+ </span>{" "}
+ | Episode {a.episode}
+ </p>
+ </Link>
+ </m.div>
+ );
+ })}
+ </div>
+ )}
+ {!loading && page > 10 && nextPage && (
+ <button
+ onClick={() => setPage((p) => p + 1)}
+ className="bg-secondary xl:w-[30%] w-[80%] h-10 rounded-md"
+ >
+ Load More
+ </button>
+ )}
+ </div>
+ {/* </div> */}
+ </div>
+ </main>
+ <Footer />
+ </>
+ );
+}